package kube

import (
	"errors"
	"fmt"
	"gitee.com/lllgo/gopkg/files"
	"gitee.com/lllgo/gopkg/template"
	"gitee.com/lllgo/gopkg/utils"
	"gitee.com/lllgo/opsctl/pkg/cmd"
	"gitee.com/lllgo/opsctl/pkg/conf"
	"gitee.com/lllgo/opsctl/pkg/tools"
	"github.com/mitchellh/mapstructure"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"path/filepath"
	"strconv"
)

const CmdName = "kube"

var (
	kubeConfig                          KubeConfig
	kubeDir, localBaseDir, kubeToolsDir string
	hostsTmpl                           template.Config
)

func init() {

	cmd.RegisterService(initConfig, kubernetesCmd, createCmd(), cleanCmd(), startCmd(), stopCmd())

}

func registerSubCmd(cmds ...*cobra.Command) {
	kubernetesCmd.AddCommand(cmds...)
}

type KubeConfig struct {
	// 元数据配置
	Meta struct {
		conf.MetaConfig
		//证书存储目录
		CertDir string
	}
	//kubeDir没有默认配置 要使用时必须通过系统环境变量配置或opsctl配置文件配置
	Dir string
	// 服务端配置
	ServerBinDir string
	ServerCADir  string

	// 服务器配置
	Master, Node, Etcd, Harbor, Lb []string
	Chrony                         string

	// 版本控制
	DockerVersion string
	KubeVersion   string

	// 扩展配置
	Container        string // docker, containerd
	Network          string // calico, flannel, kube-router, cilium, kube-ovn
	ProxyMode        string // iptables,ipvs
	ServiceCIDR      string // 10.68.0.0/16
	ClusterCIDR      string // 172.20.0.0/16
	NodePortRange    string // 20000-40000
	ClusterDNSDomain string // cluster.local.

}

func initConfig() {
	basePath := conf.GetConfigPath(CmdName)
	if basePath == "" {
		logrus.Info("找到kube，不启用kube命令")
		return
	}

	kubeConfig = KubeConfig{
		ServerBinDir:     "/opt/kube/bin",
		ServerCADir:      "/etc/kubernetes/ssl",
		DockerVersion:    "19.03.9",
		KubeVersion:      "1.18.9",
		Container:        "docker",
		Network:          "flannel",
		ProxyMode:        "ipvs",
		ServiceCIDR:      "10.68.0.0/16",
		ClusterCIDR:      "172.20.0.0/16",
		NodePortRange:    "20000-40000",
		ClusterDNSDomain: "cluster.local.",
	}

	// 不支持通过环境变量管理多套集群
	cc := conf.GetComponentConfig(CmdName)[0]
	if err := mapstructure.Decode(cc, &kubeConfig); err != nil {
		panic(err)
	}

	if kubeConfig.Dir == "" {
		panic(errors.New("请设置kube-dir"))
	} else {
		kubeConfig.Dir = files.Abs(basePath, kubeConfig.Dir)
	}

	kubeDir = files.Abs("", kubeConfig.Dir)
	kubeConfig.Meta.LocalDir = filepath.Join(utils.GetDefString(kubeConfig.Meta.LocalDir, conf.C.LocalDir), CmdName)

	if kubeConfig.Meta.CertDir == "" {
		kubeConfig.Meta.CertDir = filepath.Join(kubeConfig.Meta.LocalDir, ".cluster")
	} else {
		kubeConfig.Meta.CertDir = files.Abs(basePath, kubeConfig.Meta.CertDir)
	}

	localBaseDir = kubeConfig.Meta.LocalDir
	kubeToolsDir = filepath.Join(kubeDir, "tools")

	for i, n := range kubeConfig.Etcd {
		addEtcdDesc(n, i)
	}

	hostsTmpl = template.Config{TmplFile: filepath.Join(kubeDir, "tmpl", "hosts.tmpl"), DestFile: filepath.Join(kubeDir, "hosts")}

}

func addEtcdDesc(n string, index int) {
	for _, host := range tools.GetHosts(n) {
		host.AddDesc("NODE_NAME", "etcd"+strconv.Itoa(index))
	}
}

// kubernetesCmd represents the k8s command
var kubernetesCmd = &cobra.Command{
	Use:     "kube",
	Short:   "管理kubernetes集群",
	Long:    `管理kubernetes集群`,
	Aliases: []string{"k8s", "kb", "kubernetes"},
	Args: func(cmd *cobra.Command, args []string) error {
		var argsLen = len(args)
		if argsLen == 0 || argsLen == 1 {
			cmd.Println(cmd.UsageString())
		}

		if argsLen == 1 {
			return fmt.Errorf("kubernetes需要指定操作类型和动作")
		}
		return nil
	},
}

func genTmpl() {
	if err := hostsTmpl.Write(kubeConfig); err != nil {
		panic("渲染hosts配置文件失败 " + err.Error())
	}
}

func newAnsiblePlaybookAndRun(playbooks ...string) error {
	genTmpl()

	return tools.NewPlaybook(kubeDir, "", playbooks...).Run()
}

func newAnsiblePlaybookWithTagAndRun(tag string, playbooks ...string) error {
	genTmpl()

	playbook := tools.NewPlaybook(kubeDir, "", playbooks...)
	playbook.Tags = []string{tag}

	return playbook.Run()
}

func createCmd() *cobra.Command {
	var tmp = &cobra.Command{
		Use:   "create",
		Short: "创建集群",
		Long:  `创建集群`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return newAnsiblePlaybookAndRun("90.setup.yml")
		},
	}
	return tmp
}

func (c KubeConfig) addHostTag() {
	for tag, names := range c.getHostsName() {
		cmd.GetHostsWithTag(conf.Add, tag, names...)
	}
}

func (c KubeConfig) getHostsName() map[string][]string {
	return map[string][]string{
		"master":      c.Master,
		"node":        c.Node,
		"etcd":        c.Etcd,
		"harbor":      c.Harbor,
		"loadblancer": c.Lb,
		"chrony":      {c.Chrony},
	}
}

func (c KubeConfig) removeHostTag() {
	for tag, names := range c.getHostsName() {
		cmd.GetHostsWithTag(conf.Remove, tag, names...)
	}
}

func cleanCmd() *cobra.Command {
	var tmp = &cobra.Command{
		Use:   "clean",
		Short: "清除集群资源",
		Long: `清除集群资源
master、node、etcd、loadblancer、chrony、env、docker
`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return newAnsiblePlaybookAndRun("99.clean.yml")
		},
	}
	return tmp
}

func startCmd() *cobra.Command {
	var cmd = &cobra.Command{
		Use:   "start",
		Short: "启动集群服务",
		Long: `启动集群服务
master: kube-apiserver、kube-controller-manager、kube-scheduler、docker/containerd、haproxy
node: kubelet、kube-proxy、docker/containerd、haproxy
etcd: 
ex-lb: haproxy、keepalived
`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return newAnsiblePlaybookAndRun("91.start.yml")
		},
	}
	return cmd
}

func stopCmd() *cobra.Command {
	var cmd = &cobra.Command{
		Use:   "stop",
		Short: "停止集群服务",
		Long: `停止集群服务
master: kube-apiserver、kube-controller-manager、kube-scheduler、docker/containerd、haproxy
node: kubelet、kube-proxy、docker/containerd、haproxy
etcd: 
ex-lb: haproxy、keepalived
`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return newAnsiblePlaybookAndRun("92.stop.yml")
		},
	}
	return cmd
}
